home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / lib / python2.5 / wsgiref / validate.pyc (.txt) < prev   
Python Compiled Bytecode  |  2008-10-29  |  17KB  |  439 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.5)
  3.  
  4. """
  5. Middleware to check for obedience to the WSGI specification.
  6.  
  7. Some of the things this checks:
  8.  
  9. * Signature of the application and start_response (including that
  10.   keyword arguments are not used).
  11.  
  12. * Environment checks:
  13.  
  14.   - Environment is a dictionary (and not a subclass).
  15.  
  16.   - That all the required keys are in the environment: REQUEST_METHOD,
  17.     SERVER_NAME, SERVER_PORT, wsgi.version, wsgi.input, wsgi.errors,
  18.     wsgi.multithread, wsgi.multiprocess, wsgi.run_once
  19.  
  20.   - That HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH are not in the
  21.     environment (these headers should appear as CONTENT_LENGTH and
  22.     CONTENT_TYPE).
  23.  
  24.   - Warns if QUERY_STRING is missing, as the cgi module acts
  25.     unpredictably in that case.
  26.  
  27.   - That CGI-style variables (that don't contain a .) have
  28.     (non-unicode) string values
  29.  
  30.   - That wsgi.version is a tuple
  31.  
  32.   - That wsgi.url_scheme is 'http' or 'https' (@@: is this too
  33.     restrictive?)
  34.  
  35.   - Warns if the REQUEST_METHOD is not known (@@: probably too
  36.     restrictive).
  37.  
  38.   - That SCRIPT_NAME and PATH_INFO are empty or start with /
  39.  
  40.   - That at least one of SCRIPT_NAME or PATH_INFO are set.
  41.  
  42.   - That CONTENT_LENGTH is a positive integer.
  43.  
  44.   - That SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should
  45.     be '/').
  46.  
  47.   - That wsgi.input has the methods read, readline, readlines, and
  48.     __iter__
  49.  
  50.   - That wsgi.errors has the methods flush, write, writelines
  51.  
  52. * The status is a string, contains a space, starts with an integer,
  53.   and that integer is in range (> 100).
  54.  
  55. * That the headers is a list (not a subclass, not another kind of
  56.   sequence).
  57.  
  58. * That the items of the headers are tuples of strings.
  59.  
  60. * That there is no 'status' header (that is used in CGI, but not in
  61.   WSGI).
  62.  
  63. * That the headers don't contain newlines or colons, end in _ or -, or
  64.   contain characters codes below 037.
  65.  
  66. * That Content-Type is given if there is content (CGI often has a
  67.   default content type, but WSGI does not).
  68.  
  69. * That no Content-Type is given when there is no content (@@: is this
  70.   too restrictive?)
  71.  
  72. * That the exc_info argument to start_response is a tuple or None.
  73.  
  74. * That all calls to the writer are with strings, and no other methods
  75.   on the writer are accessed.
  76.  
  77. * That wsgi.input is used properly:
  78.  
  79.   - .read() is called with zero or one argument
  80.  
  81.   - That it returns a string
  82.  
  83.   - That readline, readlines, and __iter__ return strings
  84.  
  85.   - That .close() is not called
  86.  
  87.   - No other methods are provided
  88.  
  89. * That wsgi.errors is used properly:
  90.  
  91.   - .write() and .writelines() is called with a string
  92.  
  93.   - That .close() is not called, and no other methods are provided.
  94.  
  95. * The response iterator:
  96.  
  97.   - That it is not a string (it should be a list of a single string; a
  98.     string will work, but perform horribly).
  99.  
  100.   - That .next() returns a string
  101.  
  102.   - That the iterator is not iterated over until start_response has
  103.     been called (that can signal either a server or application
  104.     error).
  105.  
  106.   - That .close() is called (doesn't raise exception, only prints to
  107.     sys.stderr, because we only know it isn't called when the object
  108.     is garbage collected).
  109. """
  110. __all__ = [
  111.     'validator']
  112. import re
  113. import sys
  114. from types import DictType, StringType, TupleType, ListType
  115. import warnings
  116. header_re = re.compile('^[a-zA-Z][a-zA-Z0-9\\-_]*$')
  117. bad_header_value_re = re.compile('[\\000-\\037]')
  118.  
  119. class WSGIWarning(Warning):
  120.     '''
  121.     Raised in response to WSGI-spec-related warnings
  122.     '''
  123.     pass
  124.  
  125.  
  126. def assert_(cond, *args):
  127.     if not cond:
  128.         raise AssertionError(*args)
  129.     
  130.  
  131.  
  132. def validator(application):
  133.     """
  134.     When applied between a WSGI server and a WSGI application, this
  135.     middleware will check for WSGI compliancy on a number of levels.
  136.     This middleware does not modify the request or response in any
  137.     way, but will throw an AssertionError if anything seems off
  138.     (except for a failure to close the application iterator, which
  139.     will be printed to stderr -- there's no way to throw an exception
  140.     at that point).
  141.     """
  142.     
  143.     def lint_app(*args, **kw):
  144.         assert_(len(args) == 2, 'Two arguments required')
  145.         assert_(not kw, 'No keyword arguments allowed')
  146.         (environ, start_response) = args
  147.         check_environ(environ)
  148.         start_response_started = []
  149.         
  150.         def start_response_wrapper(*args, **kw):
  151.             if not len(args) == 2:
  152.                 pass
  153.             assert_(len(args) == 3, 'Invalid number of arguments: %s' % (args,))
  154.             assert_(not kw, 'No keyword arguments allowed')
  155.             status = args[0]
  156.             headers = args[1]
  157.             if len(args) == 3:
  158.                 exc_info = args[2]
  159.             else:
  160.                 exc_info = None
  161.             check_status(status)
  162.             check_headers(headers)
  163.             check_content_type(status, headers)
  164.             check_exc_info(exc_info)
  165.             start_response_started.append(None)
  166.             return WriteWrapper(start_response(*args))
  167.  
  168.         environ['wsgi.input'] = InputWrapper(environ['wsgi.input'])
  169.         environ['wsgi.errors'] = ErrorWrapper(environ['wsgi.errors'])
  170.         iterator = application(environ, start_response_wrapper)
  171.         if iterator is not None:
  172.             pass
  173.         assert_(iterator != False, 'The application must return an iterator, if only an empty list')
  174.         check_iterator(iterator)
  175.         return IteratorWrapper(iterator, start_response_started)
  176.  
  177.     return lint_app
  178.  
  179.  
  180. class InputWrapper:
  181.     
  182.     def __init__(self, wsgi_input):
  183.         self.input = wsgi_input
  184.  
  185.     
  186.     def read(self, *args):
  187.         assert_(len(args) <= 1)
  188.         v = self.input.read(*args)
  189.         assert_(type(v) is type(''))
  190.         return v
  191.  
  192.     
  193.     def readline(self):
  194.         v = self.input.readline()
  195.         assert_(type(v) is type(''))
  196.         return v
  197.  
  198.     
  199.     def readlines(self, *args):
  200.         assert_(len(args) <= 1)
  201.         lines = self.input.readlines(*args)
  202.         assert_(type(lines) is type([]))
  203.         for line in lines:
  204.             assert_(type(line) is type(''))
  205.         
  206.         return lines
  207.  
  208.     
  209.     def __iter__(self):
  210.         while None:
  211.             line = self.readline()
  212.             if not line:
  213.                 return None
  214.             
  215.             yield line
  216.             continue
  217.             return None
  218.  
  219.     
  220.     def close(self):
  221.         assert_(0, 'input.close() must not be called')
  222.  
  223.  
  224.  
  225. class ErrorWrapper:
  226.     
  227.     def __init__(self, wsgi_errors):
  228.         self.errors = wsgi_errors
  229.  
  230.     
  231.     def write(self, s):
  232.         assert_(type(s) is type(''))
  233.         self.errors.write(s)
  234.  
  235.     
  236.     def flush(self):
  237.         self.errors.flush()
  238.  
  239.     
  240.     def writelines(self, seq):
  241.         for line in seq:
  242.             self.write(line)
  243.         
  244.  
  245.     
  246.     def close(self):
  247.         assert_(0, 'errors.close() must not be called')
  248.  
  249.  
  250.  
  251. class WriteWrapper:
  252.     
  253.     def __init__(self, wsgi_writer):
  254.         self.writer = wsgi_writer
  255.  
  256.     
  257.     def __call__(self, s):
  258.         assert_(type(s) is type(''))
  259.         self.writer(s)
  260.  
  261.  
  262.  
  263. class PartialIteratorWrapper:
  264.     
  265.     def __init__(self, wsgi_iterator):
  266.         self.iterator = wsgi_iterator
  267.  
  268.     
  269.     def __iter__(self):
  270.         return IteratorWrapper(self.iterator, None)
  271.  
  272.  
  273.  
  274. class IteratorWrapper:
  275.     
  276.     def __init__(self, wsgi_iterator, check_start_response):
  277.         self.original_iterator = wsgi_iterator
  278.         self.iterator = iter(wsgi_iterator)
  279.         self.closed = False
  280.         self.check_start_response = check_start_response
  281.  
  282.     
  283.     def __iter__(self):
  284.         return self
  285.  
  286.     
  287.     def next(self):
  288.         assert_(not (self.closed), 'Iterator read after closed')
  289.         v = self.iterator.next()
  290.         if self.check_start_response is not None:
  291.             assert_(self.check_start_response, 'The application returns and we started iterating over its body, but start_response has not yet been called')
  292.             self.check_start_response = None
  293.         
  294.         return v
  295.  
  296.     
  297.     def close(self):
  298.         self.closed = True
  299.         if hasattr(self.original_iterator, 'close'):
  300.             self.original_iterator.close()
  301.         
  302.  
  303.     
  304.     def __del__(self):
  305.         if not self.closed:
  306.             sys.stderr.write('Iterator garbage collected without being closed')
  307.         
  308.         assert_(self.closed, 'Iterator garbage collected without being closed')
  309.  
  310.  
  311.  
  312. def check_environ(environ):
  313.     assert_(type(environ) is DictType, 'Environment is not of the right type: %r (environment: %r)' % (type(environ), environ))
  314.     for key in [
  315.         'REQUEST_METHOD',
  316.         'SERVER_NAME',
  317.         'SERVER_PORT',
  318.         'wsgi.version',
  319.         'wsgi.input',
  320.         'wsgi.errors',
  321.         'wsgi.multithread',
  322.         'wsgi.multiprocess',
  323.         'wsgi.run_once']:
  324.         assert_(key in environ, 'Environment missing required key: %r' % (key,))
  325.     
  326.     for key in [
  327.         'HTTP_CONTENT_TYPE',
  328.         'HTTP_CONTENT_LENGTH']:
  329.         assert_(key not in environ, 'Environment should not have the key: %s (use %s instead)' % (key, key[5:]))
  330.     
  331.     if 'QUERY_STRING' not in environ:
  332.         warnings.warn('QUERY_STRING is not in the WSGI environment; the cgi module will use sys.argv when this variable is missing, so application errors are more likely', WSGIWarning)
  333.     
  334.     for key in environ.keys():
  335.         if '.' in key:
  336.             continue
  337.         
  338.         assert_(type(environ[key]) is StringType, 'Environmental variable %s is not a string: %r (value: %r)' % (key, type(environ[key]), environ[key]))
  339.     
  340.     assert_(type(environ['wsgi.version']) is TupleType, 'wsgi.version should be a tuple (%r)' % (environ['wsgi.version'],))
  341.     assert_(environ['wsgi.url_scheme'] in ('http', 'https'), 'wsgi.url_scheme unknown: %r' % environ['wsgi.url_scheme'])
  342.     check_input(environ['wsgi.input'])
  343.     check_errors(environ['wsgi.errors'])
  344.     if environ['REQUEST_METHOD'] not in ('GET', 'HEAD', 'POST', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'):
  345.         warnings.warn('Unknown REQUEST_METHOD: %r' % environ['REQUEST_METHOD'], WSGIWarning)
  346.     
  347.     if not not environ.get('SCRIPT_NAME'):
  348.         pass
  349.     assert_(environ['SCRIPT_NAME'].startswith('/'), "SCRIPT_NAME doesn't start with /: %r" % environ['SCRIPT_NAME'])
  350.     if not not environ.get('PATH_INFO'):
  351.         pass
  352.     assert_(environ['PATH_INFO'].startswith('/'), "PATH_INFO doesn't start with /: %r" % environ['PATH_INFO'])
  353.     if environ.get('CONTENT_LENGTH'):
  354.         assert_(int(environ['CONTENT_LENGTH']) >= 0, 'Invalid CONTENT_LENGTH: %r' % environ['CONTENT_LENGTH'])
  355.     
  356.     if not environ.get('SCRIPT_NAME'):
  357.         assert_(environ.has_key('PATH_INFO'), "One of SCRIPT_NAME or PATH_INFO are required (PATH_INFO should at least be '/' if SCRIPT_NAME is empty)")
  358.     
  359.     assert_(environ.get('SCRIPT_NAME') != '/', "SCRIPT_NAME cannot be '/'; it should instead be '', and PATH_INFO should be '/'")
  360.  
  361.  
  362. def check_input(wsgi_input):
  363.     for attr in [
  364.         'read',
  365.         'readline',
  366.         'readlines',
  367.         '__iter__']:
  368.         assert_(hasattr(wsgi_input, attr), "wsgi.input (%r) doesn't have the attribute %s" % (wsgi_input, attr))
  369.     
  370.  
  371.  
  372. def check_errors(wsgi_errors):
  373.     for attr in [
  374.         'flush',
  375.         'write',
  376.         'writelines']:
  377.         assert_(hasattr(wsgi_errors, attr), "wsgi.errors (%r) doesn't have the attribute %s" % (wsgi_errors, attr))
  378.     
  379.  
  380.  
  381. def check_status(status):
  382.     assert_(type(status) is StringType, 'Status must be a string (not %r)' % status)
  383.     status_code = status.split(None, 1)[0]
  384.     assert_(len(status_code) == 3, 'Status codes must be three characters: %r' % status_code)
  385.     status_int = int(status_code)
  386.     assert_(status_int >= 100, 'Status code is invalid: %r' % status_int)
  387.     if len(status) < 4 or status[3] != ' ':
  388.         warnings.warn('The status string (%r) should be a three-digit integer followed by a single space and a status explanation' % status, WSGIWarning)
  389.     
  390.  
  391.  
  392. def check_headers(headers):
  393.     assert_(type(headers) is ListType, 'Headers (%r) must be of type list: %r' % (headers, type(headers)))
  394.     header_names = { }
  395.     for item in headers:
  396.         assert_(type(item) is TupleType, 'Individual headers (%r) must be of type tuple: %r' % (item, type(item)))
  397.         assert_(len(item) == 2)
  398.         (name, value) = item
  399.         assert_(name.lower() != 'status', 'The Status header cannot be used; it conflicts with CGI script, and HTTP status is not given through headers (value: %r).' % value)
  400.         header_names[name.lower()] = None
  401.         if '\n' not in name:
  402.             pass
  403.         assert_(':' not in name, "Header names may not contain ':' or '\\n': %r" % name)
  404.         assert_(header_re.search(name), 'Bad header name: %r' % name)
  405.         if not name.endswith('-'):
  406.             pass
  407.         assert_(not name.endswith('_'), "Names may not end in '-' or '_': %r" % name)
  408.         if bad_header_value_re.search(value):
  409.             assert_(0, 'Bad header value: %r (bad char: %r)' % (value, bad_header_value_re.search(value).group(0)))
  410.             continue
  411.     
  412.  
  413.  
  414. def check_content_type(status, headers):
  415.     code = int(status.split(None, 1)[0])
  416.     NO_MESSAGE_BODY = (204, 304)
  417.     for name, value in headers:
  418.         if name.lower() == 'content-type':
  419.             if code not in NO_MESSAGE_BODY:
  420.                 return None
  421.             
  422.             assert_(0, 'Content-Type header found in a %s response, which must not return content.' % code)
  423.             continue
  424.     
  425.     if code not in NO_MESSAGE_BODY:
  426.         assert_(0, 'No Content-Type header found in headers (%s)' % headers)
  427.     
  428.  
  429.  
  430. def check_exc_info(exc_info):
  431.     if not exc_info is None:
  432.         pass
  433.     assert_(type(exc_info) is type(()), 'exc_info (%r) is not a tuple: %r' % (exc_info, type(exc_info)))
  434.  
  435.  
  436. def check_iterator(iterator):
  437.     assert_(not isinstance(iterator, str), 'You should not return a string as your application iterator, instead return a single-item list containing that string.')
  438.  
  439.